//--------------------------------------------------------------------------------
// This file is a portion of the D2D1Renderer.  It is distributed
// under the MIT License, available in the root of this distribution and 
// at the following URL:
//
// http://www.opensource.org/licenses/mit-license.php
//
// Copyright (c) Mirco Timmermann
//
// Sources: http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.ht
//--------------------------------------------------------------------------------
#include "Direct2DContext.h"
#include <math.h>

#include "Image.h"

#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif

//Forward Declaration
std::wstring s2ws(const std::string& s);
std::string ws2s(const std::wstring& s);


Direct2DContext::Direct2DContext(ID2D1RenderTarget* renderTarget, ID2D1Factory* d2dFactory, IDWriteFactory* dWriteFactory, IDWriteTextFormat* textFormat) {
	_renderTarget = renderTarget;
	_d2dFactory = d2dFactory;
	_dWriteFactory = dWriteFactory;
	_textFormat = textFormat;

	_solidColorBrush_stroke = 0;
	_solidColorBrush_fill = 0;

	_linearGradientBrush_fill = 0;
	_linearGradientBrush_stroke = 0;

	_strokeBrush = 0;
	_fillBrush = 0;

	_path = new Path(d2dFactory);
	createD2DBrushes();
}

Direct2DContext::~Direct2DContext(void) {
	if(_path != 0) {
		delete _path;
	}

	if(_linearGradientBrush_fill != 0) {
		delete _linearGradientBrush_fill;
		_linearGradientBrush_fill = 0;
	}

	if(_linearGradientBrush_stroke != 0) {
		delete _linearGradientBrush_stroke;
		_linearGradientBrush_stroke = 0;
	}
	
	if(_solidColorBrush_stroke != 0) {
		_solidColorBrush_stroke->Release();
		_solidColorBrush_stroke = 0;
	}

	if(_solidColorBrush_fill != 0) {
		_solidColorBrush_fill->Release();
		_solidColorBrush_fill = 0;
	}
}

void Direct2DContext::Resize(int width, int height) {
    if (_renderTarget) {
		ID2D1HwndRenderTarget* hwndRenderTarget = dynamic_cast<ID2D1HwndRenderTarget*>(_renderTarget);
		if(hwndRenderTarget != 0) {
			hwndRenderTarget->Resize(D2D1::SizeU(width, height));
		}   

		/*
		hr = m_pD2DFactory->CreateHwndRenderTarget(
    D2D1::RenderTargetProperties(),
    D2D1::HwndRenderTargetProperties(m_hwnd, size),
    &m_pRenderTarget
    );
		*/
    }
}

///////////////////////////////////////////////////////////////////////////////////////////////
//                                  DRAW UTILS CREATION                                      //
///////////////////////////////////////////////////////////////////////////////////////////////

/*
* creates a brush.
*/
void Direct2DContext::createD2DBrushes() {
	HRESULT hr;

	hr = _renderTarget->CreateSolidColorBrush(
		D2D1::ColorF(0.0f, 0.0f, 0.0f, 1.0f), 
		&_solidColorBrush_stroke
	);
	if(FAILED(hr)) {
		//MessageBox(NULL, "createD2DBrushes::CreateSolidColorBrush failed", "Direct2DContext", 0);
	}

	_strokeBrush = _solidColorBrush_stroke; //default stroke

	hr = _renderTarget->CreateSolidColorBrush(
		D2D1::ColorF(0.0f, 0.0f, 0.0f, 1.0f), 
		&_solidColorBrush_fill
	);
	if(FAILED(hr)) {
		//MessageBox(NULL, "createD2DBrushes::CreateSolidColorBrush failed", "Direct2DContext", 0);
	}

	_fillBrush = _solidColorBrush_fill; //default fill

	//
	_linearGradientBrush_fill = new LinearGradientBrush(_renderTarget);
	_linearGradientBrush_stroke = new LinearGradientBrush(_renderTarget);
}

///////////////////////////////////////////////////////////////////////////////////////////////
//                                    DRAW FUNCTIONS                                         //
///////////////////////////////////////////////////////////////////////////////////////////////

void Direct2DContext::draw_strokePath(Path& path) {
	_renderTarget->DrawGeometry(path.GetGeometry(), _strokeBrush, (float)lineWidth);
}

void Direct2DContext::draw_fillPath(Path& path) {
	_renderTarget->FillGeometry(path.GetGeometry(), _fillBrush);
}

void Direct2DContext::draw_strokeText(std::string text, float x, float y, float maxWidth) {
    // Retrieve the size of the render target.
    D2D1_SIZE_F renderTargetSize = _renderTarget->GetSize();

	std::wstring a = s2ws(text); //L"Hello, World!";

	//
	D2D1::Matrix3x2F mtx;
	_renderTarget->GetTransform(&mtx);

	D2D1::Matrix3x2F tempMatrix = D2D1::Matrix3x2F::Matrix3x2F(mtx._11, mtx._12, mtx._21, mtx._22, mtx._31, mtx._32);

	D2D1::Matrix3x2F local = D2D1::Matrix3x2F::Translation(x, y);

	_renderTarget->SetTransform(local*mtx);

	_renderTarget->DrawText(
			a.c_str(),
			a.size(), //-1 -> NULL-Byte at the end of the WCHAR string.
            _textFormat,
            D2D1::RectF(0, 0, renderTargetSize.width, renderTargetSize.height),
            _solidColorBrush_stroke
    );

	_renderTarget->SetTransform(tempMatrix);
}

///////////////////////////////////////////////////////////////////////////////////////////////
//                                        PROPERTIES                                         //
///////////////////////////////////////////////////////////////////////////////////////////////

/*@DEPRICATED
* TODO: ERSETZEN DURCH PROPERTY ctx->strokeStyle = "#FF0000";
*/
void Direct2DContext::SetStrokeStyle(float r, float g, float b) {
	_solidColorBrush_stroke->SetColor(D2D1::ColorF(r, g, b, (float)globalAlpha));

	_strokeBrush = _solidColorBrush_stroke;
}

void Direct2DContext::SetStrokeStyle(Color c) {
	_solidColorBrush_stroke->SetColor(D2D1::ColorF(c.r, c.g, c.b, (float)globalAlpha));

	_strokeBrush = _solidColorBrush_stroke;
}

void Direct2DContext::SetStrokeStyle(CanvasGradient canvasGradient) {
	_linearGradientBrush_stroke->SetCanvasGradient(canvasGradient);

	_strokeBrush = _linearGradientBrush_stroke->GetGradientBrush();
}

/*@DEPRICATED
* TODO: ERSETZEN DURCH PROPERTY ctx->fillStyle = "#FF0000";
*/
void Direct2DContext::SetFillStyle(float r, float g, float b) {
	_solidColorBrush_fill->SetColor(D2D1::ColorF(r, g, b, (float)globalAlpha));

	_fillBrush = _solidColorBrush_fill;
}

void Direct2DContext::SetFillStyle(Color color) {
	_solidColorBrush_fill->SetColor(D2D1::ColorF(color.r, color.g, color.b, (float)globalAlpha));

	_fillBrush = _solidColorBrush_fill;
}

void Direct2DContext::SetFillStyle(CanvasGradient canvasGradient) {
	_linearGradientBrush_fill->SetCanvasGradient(canvasGradient);

	_fillBrush = _linearGradientBrush_fill->GetGradientBrush();
}

///////////////////////////////////////////////////////////////////////////////////////////////
//                                     DRAW INTERFACE (HTML5-Standard)                       //
///////////////////////////////////////////////////////////////////////////////////////////////

void Direct2DContext::save() {
	D2D1::Matrix3x2F mtx;
	_renderTarget->GetTransform(&mtx);

	Save save;
	save._matrix = D2D1::Matrix3x2F(mtx);
	save._alpha = this->globalAlpha;

	_saveRestoreStack.push(save);
}

void Direct2DContext::restore() {
	Save save = _saveRestoreStack.top();
	_saveRestoreStack.pop();

	_renderTarget->SetTransform(save._matrix);
	this->globalAlpha = save._alpha;
}

void Direct2DContext::clearRect(float x, float y, float w, float h) {

}

void Direct2DContext::fillRect(float x, float y, float w, float h) {
	rect(x, y, w, h);
	_renderTarget->FillRectangle(_rect, _fillBrush);
}

void Direct2DContext::strokeRect(float x, float y, float w, float h) {
	rect(x, y, w, h);
	_renderTarget->DrawRectangle(_rect, _strokeBrush, lineWidth);
}

void Direct2DContext::beginPath() {
	_path->Begin();
}

void Direct2DContext::fill() {
	_path->end(); //prevent for not been closed.

	this->draw_fillPath(*_path);
}

void Direct2DContext::stroke() {
	_path->end(); //prevent for not been closed.

	this->draw_strokePath(*_path);
}

void Direct2DContext::clip() {

}

void Direct2DContext::fillText(std::string text, float x, float y, float maxWidth) {
	draw_strokeText(text, x, y, maxWidth);
}

void Direct2DContext::strokeText(std::string text, float x, float y, float maxWidth) {
	draw_strokeText(text, x, y, maxWidth);
}

///////////////////////////////////////////////////////////////////////////////////////////////
//                                  TRANSFORM INTERFACE (~HTML5-Standard)                    //
///////////////////////////////////////////////////////////////////////////////////////////////

/*
* Changes the transformation matrix to apply a scaling transformation with the given characteristics.
*/
void Direct2DContext::scale(float x, float y) {
	D2D1::Matrix3x2F scale = D2D1::Matrix3x2F::Scale(x, y);

	D2D1::Matrix3x2F mtx;
	_renderTarget->GetTransform(&mtx);

	_renderTarget->SetTransform(scale*mtx);
}

/*
* Changes the transformation matrix to apply a rotation transformation with the given characteristics. The angle is in radians.
*/
void Direct2DContext::rotate(float angle) {
	D2D1::Matrix3x2F rot = D2D1::Matrix3x2F::Rotation(angle);

	D2D1::Matrix3x2F mtx;
	_renderTarget->GetTransform(&mtx);

	_renderTarget->SetTransform(rot*mtx);
}

/*
* Changes the transformation matrix to apply a translation transformation with the given characteristics.
*/
void Direct2DContext::translate(float x, float y) {
	D2D1::Matrix3x2F trans = D2D1::Matrix3x2F::Translation(x, y);

	D2D1::Matrix3x2F mtx;
	_renderTarget->GetTransform(&mtx);

	_renderTarget->SetTransform(trans*mtx);
}

//http://msdn.microsoft.com/en-us/library/windows/desktop/dd372281%28v=vs.85%29.aspx
/*
* Changes the transformation matrix to apply the matrix given by the arguments as described below.
*/
void Direct2DContext::transform(float a, float b, float c, float d, float e, float f) {
	D2D1::Matrix3x2F matrix = D2D1::Matrix3x2F::Matrix3x2F(a, b, c, d, e, f);

	D2D1::Matrix3x2F mtx;
	_renderTarget->GetTransform(&mtx);

	_renderTarget->SetTransform(matrix*mtx);
}

/*
* Changes the transformation matrix to the matrix given by the arguments as described below.
*/
//http://msdn.microsoft.com/en-us/library/windows/desktop/dd372281%28v=vs.85%29.aspx
void Direct2DContext::setTransform(float a, float b, float c, float d, float e, float f) {
	D2D1::Matrix3x2F matrix = D2D1::Matrix3x2F::Matrix3x2F(a, b, c, d, e, f);
	_renderTarget->SetTransform(matrix);
}

///////////////////////////////////////////////////////////////////////////////////////////////
//                                     PATH INTERFACE (~HTML5-Standard)                      //
///////////////////////////////////////////////////////////////////////////////////////////////

void Direct2DContext::closePath() {
	//_path->end(); //TODO: Problem: siehe Checkbox reihenfolge stroke()/closePath() 
	_path->close();
}

/*
* moves the pencil to the specified starting point. 
*/
void Direct2DContext::moveTo(float x, float y) {
	_path->Move(x, y);
}

/*
* draws a line to the specified ending point.
*/
void Direct2DContext::lineTo(float x, float y) {
	_path->Add(x, y);
}

void Direct2DContext::quadraticCurveTo(float cpx, float cpy, float x, float y) {
	_path->AddQuadraticBezier(cpx, cpy, x, y);
}

void Direct2DContext::bezierCurveTo(float cp1x, float cp1y, float cp2x, float cp2y, float x, float y) {
	_path->AddBezier(cp1x, cp1y, cp2x, cp2y, x, y);
}

void Direct2DContext::arcTo(float x1, float y1, float x2, float y2, float radius) {
	//_path->AddArc()

	//TODO:
	//_path->AddArc(x1, y1, x2, y2, radius);
}

CanvasGradient Direct2DContext::createLinearGradient(float x0, float y0, float x1, float y1) {
	return CanvasGradient(x0, y0, x1, y1);
}

CanvasGradient Direct2DContext::createRadialGradient(float x0, float y0, float r0, float x1, float y1, float r1) {
	return CanvasGradient(x0, y0, x1, y1); //TODO:
}

/*
* TODO: path verwenden anstatt direct2D rectangle!?
*/
void Direct2DContext::rect(float x, float y, float w, float h) {
	this->beginPath();

	//rect definition
	D2D1_RECT_F rect;
	rect.left = x; 
	rect.top = y;
	rect.right = x + w;
	rect.bottom = y + h;

	_rect = rect;

	//path definition
	this->moveTo(x, y);
	this->lineTo(x + w, y);
	this->lineTo(x + w, y + h);
	this->lineTo(x, y + h);

	this->closePath();
}

void Direct2DContext::arc(float x, float y, float radius, float startAngle, float endAngle, bool anticlockwise) {
	_path->AddArc(x, y, radius, startAngle, endAngle, anticlockwise);
}

///////////////////////////////////////////////////////////////////////////////////////////////
//                                                                                           //
///////////////////////////////////////////////////////////////////////////////////////////////

Direct2DContext::Dimension Direct2DContext::measureText(std::string text) {
	IDWriteTextLayout* textLayout;

	wchar_t* text_wchar = new wchar_t[text.size()];
	for(std::string::size_type i = 0; i < text.size(); ++i) {
		text_wchar[i] = text[i];
	}

	_dWriteFactory->CreateTextLayout(text_wchar, text.length(), _textFormat, 50000, 50000, &textLayout);

	delete text_wchar;

	DWRITE_TEXT_METRICS metrics;
	textLayout->GetMetrics( &metrics );
	textLayout->Release();

	Dimension dim;
	dim.width = metrics.widthIncludingTrailingWhitespace;
	dim.height = metrics.height;

	return dim;
}

///////////////////////////////////////////////////////////////////////////////////////////////
//                                     IMAGE INTERFACE                                       //
///////////////////////////////////////////////////////////////////////////////////////////////

void Direct2DContext::drawImage(Image* image, int dx, int dy) {
	D2D1::Matrix3x2F mtx;
	_renderTarget->GetTransform(&mtx);

	D2D1::Matrix3x2F tempMatrix = D2D1::Matrix3x2F::Matrix3x2F(mtx._11, mtx._12, mtx._21, mtx._22, mtx._31, mtx._32);

	D2D1::Matrix3x2F local = D2D1::Matrix3x2F::Translation((float)dx, (float)dy);

	_renderTarget->SetTransform(local*mtx);

	_renderTarget->DrawBitmap(image->getBitmap(), image->getRect()); //_renderTarget->DrawBitmap(image->getBitmap(), rect, 1.0, D2D1_BITMAP_INTERPOLATION_MODE::D2D1_BITMAP_INTERPOLATION_MODE_LINEAR, image->getRect()); //, &rect);

	_renderTarget->SetTransform(tempMatrix);
}

void Direct2DContext::drawImage(Image* image, int dx, int dy, int dw, int dh) {
	_renderTarget->DrawBitmap(image->getBitmap(), image->getRect());
}

void Direct2DContext::drawImage(Image* image, int sx, int sy, int sw, int sh, int dx, int dy, int dw, int dh) {
	_renderTarget->DrawBitmap(image->getBitmap(), image->getRect());
}

/////////////////////////////TEXT HELPER////////////////////////////////

//String to WCHAR
std::wstring s2ws(const std::string& s) {
	int len;
	int slength = (int)s.length() + 1;
	len = MultiByteToWideChar(CP_ACP, 0, s.c_str(), slength, 0, 0);
	wchar_t* buf = new wchar_t[len];
	MultiByteToWideChar(CP_ACP, 0, s.c_str(), slength, buf, len);
	std::wstring r(buf);
	delete[] buf;
	return r;
}

//WCHAR to String
std::string ws2s(const std::wstring& s) {
	int len;
	int slength = (int)s.length() + 1;
	len = WideCharToMultiByte(CP_ACP, 0, s.c_str(), slength, 0, 0, 0, 0);
	char* buf = new char[len];
	WideCharToMultiByte(CP_ACP, 0, s.c_str(), slength, buf, len, 0, 0);
	std::string r(buf);
	delete[] buf;
	return r;
}

///////////////////////////////////////////////////////////////////////////////////////////////
//                                         EXTENTION                                         //
///////////////////////////////////////////////////////////////////////////////////////////////

/*
void Direct2DContext::getImageData(float sx, float sy, float sw, float sh) const {

}
*/